iT邦幫忙

2021 iThome 鐵人賽

DAY 13
0

擴充 Record Mapper

大部分 domain 的 mapper 皆為 key 的轉換,而在此範例中使用的外部資源為 ActiveRecord,他大部分的界面支援 Ruby 的原生物件 hash,因此可以如下擴充 base class ,讓 mapper 裡專注在處理 entity hash key 對 schema hash key 的 mapping。

class Mapper < Boxenn::Repositories::RecordMapper
  def build(hash)
    result = batch_mapping(value: hash, mapper: map)
    result = custom_mapping(input: hash, result: result) if respond_to?(:custom_mapping, :include_private)
    result
  end

  def batch_mapping(value:, mapper:)
    case value
    when Array
      value.map do |item|
        mapping(hash: item, mapper: mapper)
      end
    when Hash
      mapping(hash: value, mapper: mapper)
    end
  end

  def mapping(hash:, mapper:)
    result = {}
    hash.each do |key, value|
      next if !mapper.key?(key)

      if mapper[key].is_a?(Array)
        result[mapper[key].first] = batch_mapping(value: value, mapper: mapper[key][1])
      else
        result[mapper[key]] = value.is_a?(Hash) ? value.compact : value
      end
    end
    result
  end
end

範例

# User
# input (entity 的 hash)
input = {
	id_number: 'A123456789',
	profile: {
		name: '王小明',
		gender: :male,
		birthday: '1998/01/01',
		email: 'abc@gamil.com', 
	},
}

# output (schema 的 hash)
output = {
	id_number: 'A123456789',
	user_profile: {
		name: '王小明',
		gender: :male,
		birthdate: '1998/01/01',
		email: 'abc@gamil.com', 
		age: 23,
	}
}

# mapper
class UserMapper < Mapper
	# 繼承此 Mapper 可以定義兩個 method, map 和 custom_mapping
	# custom_mapping 只有在需要改值或做邏輯處理時才需要定義

	private

  # map 是用來將 entity hash 轉為 db schema hash
  # 如果遇到 nested 的 hash 請使用 array,第一個值為 key, 後面則為對應 hash 裡的 key 配對

	def map
		{
			id_number: :id_number,
			profile: [
				:user_profile,
				{
					name: :name,
					gender: :gender,
					birthday: :birthdate,
					email: :email,
				},
			]
		}
	end

	# custom_mapping 需傳入兩個參數, input 和 result
  # input 為 entity 轉成的 hash
  # result 為已經轉為 db schema key 的 hash
  # 最後回傳的是再特製處理過的 db schema key 的 hash

	def custom_mapping(input:, result:)
		result[:user_profile][:age] = age(input[:profile][:birthday])
		result
	end
	
	def age(birthdate)
		today = Time.zone.today
    gap = today.year - birthdate.year
    gap = gap - 1 if (
      birthdate.month >  today.month or 
        (birthdate.month >= today.month and birthdate.day > today.day)
    )
    gap
	end
end

mapper = UserMapper.new
mapper.build(input) # => output

下一篇會介紹包裝外部資源的 Source Wrapper。


上一篇
[Day12] Boxenn 實作 Record Mapper 與 Factory
下一篇
[Day14] Boxenn 實作 Source Wrapper
系列文
在 Ruby on Rails 中導入 Domain-Driven Design 是不是搞錯了什麼?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言